#ifndef NOSAL_CORE_RESULT_H_
#define NOSAL_CORE_RESULT_H_

#include "nosal/core/error_code.h"
#include "nosal/core/optional.h"
#include "nosal/core/abort.h"

#include <utility>
#include <memory>
#include <type_traits>

namespace netaos {
namespace nosal {
namespace core {

namespace internal
{

// This class holds the actual low-level union, and manage copying, moving, and destruction of it.
template <typename T,
    typename E,
    bool TrivialDestructor = std::is_trivially_destructible<T>::value&& std::is_trivially_destructible<E>::value>
struct ResultStorage final
{
    explicit ResultStorage(T const& t)
        : mValue(t)
        , mIsValue(true)
    { }

    explicit ResultStorage(T&& t)
        : mValue(std::move(t))
        , mIsValue(true)
    { }

    explicit ResultStorage(E const& e)
        : mError(e)
        , mIsValue(false)
    { }

    explicit ResultStorage(E&& e)
        : mError(std::move(e))
        , mIsValue(false)
    { }

    ResultStorage(ResultStorage const& other)
        : mIsValue(other.mIsValue)
    {
        if (mIsValue) {
            new (&mValue) T(other.mValue);
        } else {
            new (&mError) E(other.mError);
        }
    }

    ResultStorage(ResultStorage&& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_constructible<E>::value)
        : mIsValue(other.mIsValue)
    {
        if (mIsValue) {
            new (&mValue) T(std::move(other.mValue));
        } else {
            new (&mError) E(std::move(other.mError));
        }
    }

    ResultStorage& operator=(ResultStorage const& other)
    {
        if (mIsValue && other.mIsValue) {
            mValue = other.mValue;
        } else if (!mIsValue && !other.mIsValue) {
            mError = other.mError;
        } else {
            if (other.mIsValue) {
                mError.~E();
                new (&mValue) T(other.mValue);
                mIsValue = true;
            } else {
                mValue.~T();
                new (&mError) E(other.mError);
                mIsValue = false;
            }
        }
        return *this;
    }

    ResultStorage& operator=(ResultStorage&& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_assignable<T>::value&&
            std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        if (mIsValue && other.mIsValue) {
            mValue = std::move(other.mValue);
        } else if (!mIsValue && !other.mIsValue) {
            mError = std::move(other.mError);
        } else {
            if (other.mIsValue) {
                mError.~E();
                new (&mValue) T(std::move(other.mValue));
                mIsValue = true;
            } else {
                mValue.~T();
                new (&mError) E(std::move(other.mError));
                mIsValue = false;
            }
        }
        return *this;
    }

    ~ResultStorage()
    {
        if (mIsValue) {
            mValue.~T();
        } else {
            mError.~E();
        }
    }

    void Swap(ResultStorage& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_assignable<T>::value&&
            std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        using std::swap;

        if (mIsValue && other.mIsValue) {
            swap(mValue, other.mValue);
        } else if (!mIsValue && !other.mIsValue) {
            swap(mError, other.mError);
        } else {
            if (other.mIsValue) {
                E tmp(std::move(mError));
                mError.~E();
                new (&mValue) T(std::move(other.mValue));
                other.mValue.~T();
                new (&other.mError) E(std::move(tmp));
            } else {
                E tmp(std::move(other.mError));
                other.mError.~E();
                new (&other.mValue) T(std::move(mValue));
                mValue.~T();
                new (&mError) E(std::move(tmp));
            }
            swap(mIsValue, other.mIsValue);
        }
    }

    union
    {
        T mValue;
        E mError;
    };
    bool mIsValue;
};

template <typename T, typename E>
struct ResultStorage<T,
    E,
    true  // TrivialDestructor
    >
    final
{
    constexpr explicit ResultStorage(T const& t)
        : mValue(t)
        , mIsValue(true)
    { }

    constexpr explicit ResultStorage(T&& t)
        : mValue(std::move(t))
        , mIsValue(true)
    { }

    constexpr explicit ResultStorage(E const& e)
        : mError(e)
        , mIsValue(false)
    { }

    constexpr explicit ResultStorage(E&& e)
        : mError(std::move(e))
        , mIsValue(false)
    { }

    ResultStorage(ResultStorage const& other)
        : mIsValue(other.mIsValue)
    {
        if (mIsValue) {
            new (&mValue) T(other.mValue);
        } else {
            new (&mError) E(other.mError);
        }
    }

    ResultStorage(ResultStorage&& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_constructible<E>::value)
        : mIsValue(other.mIsValue)
    {
        if (mIsValue) {
            new (&mValue) T(std::move(other.mValue));
        } else {
            new (&mError) E(std::move(other.mError));
        }
    }

    ResultStorage& operator=(ResultStorage const& other)
    {
        if (mIsValue && other.mIsValue) {
            mValue = other.mValue;
        } else if (!mIsValue && !other.mIsValue) {
            mError = other.mError;
        } else {
            if (other.mIsValue) {
                mError.~E();
                new (&mValue) T(other.mValue);
                mIsValue = true;
            } else {
                mValue.~T();
                new (&mError) E(other.mError);
                mIsValue = false;
            }
        }
        return *this;
    }

    ResultStorage& operator=(ResultStorage&& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_assignable<T>::value&&
            std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        if (mIsValue && other.mIsValue) {
            mValue = std::move(other.mValue);
        } else if (!mIsValue && !other.mIsValue) {
            mError = std::move(other.mError);
        } else {
            if (other.mIsValue) {
                mError.~E();
                new (&mValue) T(std::move(other.mValue));
                mIsValue = true;
            } else {
                mValue.~T();
                new (&mError) E(std::move(other.mError));
                mIsValue = false;
            }
        }
        return *this;
    }

    void Swap(ResultStorage& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_assignable<T>::value&&
            std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        using std::swap;

        if (mIsValue && other.mIsValue) {
            swap(mValue, other.mValue);
        } else if (!mIsValue && !other.mIsValue) {
            swap(mError, other.mError);
        } else {
            if (other.mIsValue) {
                E tmp(std::move(mError));
                mError.~E();
                new (&mValue) T(std::move(other.mValue));
                other.mValue.~T();
                new (&other.mError) E(std::move(tmp));
            } else {
                E tmp(std::move(other.mError));
                other.mError.~E();
                new (&other.mValue) T(std::move(mValue));
                mValue.~T();
                new (&mError) E(std::move(tmp));
            }
            swap(mIsValue, other.mIsValue);
        }
    }

    ~ResultStorage() = default;

    union
    {
        T mValue;
        E mError;
    };
    bool mIsValue;
};

}  // namespace internal

template <typename T, typename E = ErrorCode>
class Result final
{
    internal::ResultStorage<T, E> mStorage;

    template <typename U, typename... Args>
    struct has_as_first_checker;

    template <typename U>
    struct has_as_first_checker<U> : std::false_type
    { };

    template <typename U, typename... Args>
    struct has_as_first_checker<U, U, Args...> : std::true_type
    { };

    template <typename U, typename A, typename... Args>
    struct has_as_first_checker<U, A, Args...> : std::false_type
    { };

    template <typename U, typename... Args>
    struct has_as_first
        : public has_as_first_checker<typename std::remove_reference<U>::type,
              typename std::remove_reference<Args>::type...>
    { };

public:
    using value_type = T;
    using error_type = E;

    static Result FromValue(const T& t)
    {
        return Result(t);
    }

    static Result FromValue(T&& t)
    {
        return Result(std::move(t));
    }

    template <typename... Args,
        typename = typename std::enable_if<true  //
            && std::is_constructible<T, Args&&...>::value  //
            && !has_as_first<T, Args...>::value  //
            && !has_as_first<Result, Args...>::value  //
            >::type  //
        >
    static Result FromValue(Args&&... args)
    {
        return Result(T{std::forward<Args>(args)...});
    }

    static Result FromError(const E& e)
    {
        return Result(e);
    }

    static Result FromError(E&& e)
    {
        return Result(std::move(e));
    }

    template <typename... Args,
        typename = typename std::enable_if<true  //
            && std::is_constructible<E, Args&&...>::value  //
            && !has_as_first<E, Args...>::value  //
            && !has_as_first<Result, Args...>::value  //
            >::type  //
        >
    static Result FromError(Args&&... args)
    {
        return Result(E{std::forward<Args>(args)...});
    }

    constexpr Result(const T& t)
        : mStorage(t)
    { }

    constexpr Result(T&& t)
        : mStorage(std::move(t))
    { }

    constexpr explicit Result(const E& e)
        : mStorage(e)
    { }

    constexpr explicit Result(E&& e)
        : mStorage(std::move(e))
    { }

    template <typename... Args>
    void EmplaceValue(Args&&... args)
    {
        if (HasValue()) {
            mStorage.mValue.~T();
        } else {
            mStorage.mError.~E();
            mStorage.mIsValue = true;
        }
        new (&mStorage.mValue) T(std::forward<Args>(args)...);
    }

    template <typename... Args>
    void EmplaceError(Args&&... args)
    {
        if (HasValue()) {
            mStorage.mValue.~T();
            mStorage.mIsValue = false;
        } else {
            mStorage.mError.~E();
        }
        new (&mStorage.mError) E(std::forward<Args>(args)...);
    }

    void Swap(Result& other) noexcept(
        std::is_nothrow_move_constructible<T>::value&& std::is_nothrow_move_assignable<T>::value&&
            std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        mStorage.Swap(other.mStorage);
    }

    bool HasValue() const noexcept
    {
        return mStorage.mIsValue;
    }

    explicit operator bool() const noexcept
    {
        return HasValue();
    }

    const T& operator*() const&
    {
        return mStorage.mValue;
    }

    T&& operator*() &&
    {
        return std::move(mStorage.mValue);
    }

    const T* operator->() const
    {
        return std::addressof(mStorage.mValue);
    }

    const T& Value() const&
    {
        if (mStorage.mIsValue)
            return mStorage.mValue;
        Abort("not a value");
    }

    T&& Value() &&
    {
        if (mStorage.mIsValue)
            return std::move(mStorage.mValue);
        Abort("not a value");
    }

    const E& Error() const&
    {
        if (!mStorage.mIsValue)
            return mStorage.mError;
        Abort("not an error");
    }

    E&& Error() &&
    {
        if (!mStorage.mIsValue)
            return std::move(mStorage.mError);
        Abort("not an error");
    }

    Optional<T> Ok() const&
    {
        return HasValue() ? Optional<T>(Value()) : nullopt;
    }

    Optional<T> Ok() &&
    {
        return HasValue() ? Optional<T>(std::move(Value())) : nullopt;
    }

    Optional<E> Err() const&
    {
        return HasValue() ? nullopt : Optional<E>(Error());
    }

    Optional<E> Err() &&
    {
        return HasValue() ? nullopt : Optional<E>(std::move(Error()));
    }

    template <typename U>
    T ValueOr(U&& defaultValue) const&
    {
        return HasValue() ? Value() : static_cast<T>(std::forward<U>(defaultValue));
    }

    template <typename U>
    T ValueOr(U&& defaultValue) &&
    {
        return HasValue() ? std::move(Value()) : static_cast<T>(std::forward<U>(defaultValue));
    }

    template <typename G>
    E ErrorOr(G&& defaultError) const&
    {
        return HasValue() ? static_cast<E>(std::forward<G>(defaultError)) : Error();
    }

    template <typename G>
    E ErrorOr(G&& defaultError) &&
    {
        return HasValue() ? static_cast<E>(std::forward<G>(defaultError)) : std::move(Error());
    }

    template <typename G>
    bool CheckError(G&& error) const
    {
        return HasValue() ? false : (Error() == static_cast<E>(std::forward<G>(error)));
    }

#ifndef NOSAL_NO_EXCEPTIONS
    const T& ValueOrThrow() const& noexcept(false)
    {
        if (HasValue())
            return Value();
        Error().ThrowAsException();
    }

    T&& ValueOrThrow() && noexcept(false)
    {
        if (HasValue())
            return std::move(mStorage.mValue);
        Error().ThrowAsException();
    }
#endif

    template <typename F>
    T Resolve(F&& f) const
    {
        return HasValue() ? Value() : std::forward<F>(f)(Error());
    }

private:
    /// @brief Trait that detects whether a type is a Result<...>
    template <typename U>
    struct is_result : std::false_type
    { };

    template <typename U, typename G>
    struct is_result<Result<U, G>> : std::true_type
    { };

    template <typename F>
    using CallableReturnsResult = std::enable_if_t<is_result<std::result_of_t<F(T const&)>>::value>;

    template <typename F>
    using CallableReturnsNoResult = std::enable_if_t<!is_result<std::result_of_t<F(T const&)>>::value>;

public:
    template <typename F, typename = CallableReturnsResult<F>>
    auto Bind(F&& f) const -> decltype(f(Value()))
    {
        using U = decltype(f(Value()));
        return HasValue() ? std::forward<F>(f)(Value()) : U(Error());
    }

    template <typename F, typename = CallableReturnsNoResult<F>>
    auto Bind(F&& f) const -> Result<decltype(f(Value())), E>
    {
        using U = decltype(f(Value()));
        using R = Result<U, E>;
        return HasValue() ? std::forward<F>(f)(Value()) : R(Error());
    }

    template <typename, typename>
    friend class Result;
};

template <typename E>
class Result<void, E> final
{
    struct Dummy
    { };

    using T = Dummy;

    internal::ResultStorage<Dummy, E> mStorage;

public:
    using value_type = void;
    using error_type = E;

    static Result FromValue() noexcept
    {
        return Result();
    }

    static Result FromError(const E& e)
    {
        return Result(e);
    }

    static Result FromError(E&& e)
    {
        return Result(std::move(e));
    }

    template <typename... Args>
    static Result FromError(Args&&... args)
    {
        return Result(E{std::forward<Args>(args)...});
    }

    Result() noexcept
        : mStorage(Dummy{})
    { }

    explicit Result(const E& e)
        : mStorage(e)
    { }

    explicit Result(E&& e)
        : mStorage(std::move(e))
    { }

    template <typename... Args>
    void EmplaceValue(Args&&... args) noexcept
    {
        if (HasValue()) {
            mStorage.mValue.~T();
        } else {
            mStorage.mError.~E();
            mStorage.mIsValue = true;
        }
        new (&mStorage.mValue) T(std::forward<Args>(args)...);
    }

    template <typename... Args>
    void EmplaceError(Args&&... args)
    {
        if (HasValue()) {
            mStorage.mValue.~T();
            mStorage.mIsValue = false;
        } else {
            mStorage.mError.~E();
        }
        new (&mStorage.mError) E(std::forward<Args>(args)...);
    }

    void Swap(Result& other) noexcept(
        std::is_nothrow_move_constructible<E>::value&& std::is_nothrow_move_assignable<E>::value)
    {
        mStorage.Swap(other.mStorage);
    }

    bool HasValue() const noexcept
    {
        return mStorage.mIsValue;
    }

    explicit operator bool() const noexcept
    {
        return HasValue();
    }

    void operator*() const
    {
        return;
    }

    void Value() const
    {
        if (!mStorage.mIsValue) {
            Abort("not a value");
        }
    }

    const E& Error() const&
    {
        if (!mStorage.mIsValue)
            return mStorage.mError;
        Abort("not an error");
    }

    E&& Error() &&
    {
        if (!mStorage.mIsValue)
            return std::move(mStorage.mError);
        Abort("not an error");
    }

    Optional<E> Err() const&
    {
        return HasValue() ? nullopt : Optional<E>(Error());
    }

    Optional<E> Err() &&
    {
        return HasValue() ? nullopt : Optional<E>(std::move(Error()));
    }

    template <typename U>
    void ValueOr(U&& defaultValue) const
    {
        return HasValue() ? Value() : static_cast<void>(std::forward<U>(defaultValue));
    }

    template <typename G>
    E ErrorOr(G&& defaultError) const&
    {
        return HasValue() ? static_cast<E>(std::forward<G>(defaultError)) : Error();
    }

    template <typename G>
    E ErrorOr(G&& defaultError) &&
    {
        return HasValue() ? static_cast<E>(std::forward<G>(defaultError)) : std::move(Error());
    }

    template <typename G>
    bool CheckError(G&& error) const
    {
        return HasValue() ? false : (Error() == static_cast<E>(std::forward<G>(error)));
    }

#ifndef NOSAL_NO_EXCEPTIONS
    void ValueOrThrow() const noexcept(false)
    {
        if (!HasValue()) {
            Error().ThrowAsException();
        }
    }
#endif

    template <typename F>
    void Resolve(F&& f) const
    {
        return HasValue() ? Value() : std::forward<F>(f)(Error());
    }

private:
    /// @brief Trait that detects whether a type is a Result<...>
    template <typename U>
    struct is_result : std::false_type
    { };

    template <typename U, typename G>
    struct is_result<Result<U, G>> : std::true_type
    { };

    template <typename F>
    using CallableReturnsResult = std::enable_if_t<is_result<std::result_of_t<F()>>::value>;

    template <typename F>
    using CallableReturnsNoResult = std::enable_if_t<!is_result<std::result_of_t<F()>>::value>;

public:
    template <typename F, typename = CallableReturnsResult<F>>
    auto Bind(F&& f) const -> decltype(f())
    {
        using U = decltype(f());
        return HasValue() ? std::forward<F>(f)() : U(Error());
    }

    template <typename F, typename = CallableReturnsNoResult<F>>
    auto Bind(F&& f) const -> Result<decltype(f()), E>
    {
        using U = decltype(f());
        using R = Result<U, E>;
        return HasValue() ? std::forward<F>(f)() : R(Error());
    }

    template <typename, typename>
    friend class Result;
};

template <typename T, typename E>
void swap(Result<T, E>& lhs, Result<T, E>& rhs) noexcept(noexcept(lhs.Swap(rhs)))
{
    lhs.Swap(rhs);
}

// Compare two Result instances

template <typename T, typename E>
bool operator==(const Result<T, E>& lhs, const Result<T, E>& rhs)
{
    if (bool(lhs) != bool(rhs))
        return false;
    if (lhs)
        return lhs.Value() == rhs.Value();
    return lhs.Error() == rhs.Error();
}

template <typename T, typename E>
bool operator!=(const Result<T, E>& lhs, const Result<T, E>& rhs)
{
    if (bool(lhs) != bool(rhs))
        return true;
    if (lhs)
        return lhs.Value() != rhs.Value();
    return lhs.Error() != rhs.Error();
}

// Compare Result with a T

template <typename T, typename E>
bool operator==(const Result<T, E>& lhs, const T& rhs)
{
    return lhs ? *lhs == rhs : false;
}

template <typename T, typename E>
bool operator==(const T& lhs, const Result<T, E>& rhs)
{
    return rhs ? lhs == *rhs : false;
}

template <typename T, typename E>
bool operator!=(const Result<T, E>& lhs, const T& rhs)
{
    return lhs ? *lhs != rhs : true;
}

template <typename T, typename E>
bool operator!=(const T& lhs, const Result<T, E>& rhs)
{
    return rhs ? lhs != *rhs : true;
}

// Compare Result with an E

template <typename T, typename E>
bool operator==(const Result<T, E>& lhs, const E& rhs)
{
    return lhs ? false : lhs.Error() == rhs;
}

template <typename T, typename E>
bool operator==(const E& lhs, const Result<T, E>& rhs)
{
    return rhs ? false : lhs == rhs.Error();
}

template <typename T, typename E>
bool operator!=(const Result<T, E>& lhs, const E& rhs)
{
    return lhs ? true : lhs.Error() != rhs;
}

template <typename T, typename E>
bool operator!=(const E& lhs, const Result<T, E>& rhs)
{
    return rhs ? true : lhs != rhs.Error();
}

}  // namespace core
}  // namespace nosal
}  // namespace netaos

#endif  // NOSAL_CORE_RESULT_H_
